查看原文
其他

OceanBase 源码解读(十二):宏块的垃圾回收和坏块检查

乐于分享的 OceanBase 2022-12-25


此前,OceanBase 源码解读第十一篇《Location Cache 模块浅析》,为大家介绍了 observer 上的一个基础模块,为 SQL、事务、CLOG 等多个其他模块提供获取及缓存某个副本位置信息的能力的 Location Cache 模块。本期“源码解读”继续由 OceanBase 技术专家公祺为大家带来“存储层代码解读之「宏块的垃圾回收和坏块检查」”


在解读之前,大家可以先回顾一下公祺的上篇关于《存储层代码解读之「宏块存储格式」》的内容。通过走读 OceanBase 宏块的代码,我们对其存储格式有了初步的了解,知道了微块作为读 I/O 最小单元,宏块是写 I/O 的最小单元。


今天我们来考考大家,OceanBase 的垃圾回收(GC)和坏块检查是以微块还是宏块作为单位呢?答案很明显,就是宏块。因为既然是以宏块为单位写,所以肯定是以宏块为单位进行清理和检查的。和本专题前几篇文章类似,本文主要通过走读相关代码,了解宏块垃圾回收和坏块检查的原理。


* 注:本文所有的说明及代码都是基于 v3.1.0_CE_BP1(“阅读原文”可直达)版本的 OceanBase 开源代码。



任务调度


OceanBase 线程管理是封装好的,支持如下多种不同模式的线程管理:


// deps/oblib/src/lib/thread/thread_mgr.henum class TGType { INVALID, THREAD_POOL, // 线程池 OB_THREAD_POOL, // OB线程池 TIMER, // 定时任务 TIMER_GROUP, // 定时任务组 QUEUE_THREAD, // 队列线程调度 DEDUP_QUEUE, // 去重队列调度 ASYNC_TASK_QUEUE, // 异步任务队列调度};

OceanBase 宏块的垃圾回收和坏块检查主要采用 TIMER 定时任务调度,TIMER 的主要特点是启动一个后台线程,周期性的对任务进行调度。关于其他的调度方式,后续会单独介绍。


既然采用 TIMER 调度,那么其调度的时机也很明显,在启动 OBServer 进程时,就启动了一个后台线程,周期性的进行垃圾回收和坏块检查,代码如下(省略了部分和垃圾回收无关的代码)。


// src/storage/blocksstable/ob_store_file.cppint ObStoreFile::open(const bool is_physical_flashback){ int ret = OB_SUCCESS; bool is_replay_old = false;
// 打开StoreFile,并做相关初始化的动作 ... // 执行首次GC enable_mark_sweep(); mark_and_sweep(); // 启动GC定时任务线程 // 调度GC的任务 // 调度坏块检查的任务 if (OB_FAIL(TG_START(lib::TGDefIDs::StoreFileGC))) { STORAGE_LOG(WARN, "The timer has not been inited, ", K(ret)); } else if (OB_FAIL(TG_SCHEDULE(lib::TGDefIDs::StoreFileGC, gc_task_, RECYCLE_DELAY_US, true))) { STORAGE_LOG(WARN, "Fail to schedule gc task, ", K(ret)); } else if (OB_FAIL(TG_SCHEDULE(lib::TGDefIDs::StoreFileGC, inspect_bad_block_task_, INSPECT_DELAY_US, true))) { STORAGE_LOG(WARN, "Fail to schedule bad_block_inspect task, ", K(ret)); } else { // ...


垃圾回收和坏块检查线程的生命周期和 OBServer 进程是一致的,它们都会在 ObStoreFile::destroy 中停止。



宏块的垃圾回收


宏块垃圾回收的任务定义如下:


// src/storage/blocksstable/ob_store_file.h// ObTimerTask为定时任务的基类class ObStoreFileGCTask : public common::ObTimerTask {public: ObStoreFileGCTask(); virtual ~ObStoreFileGCTask(); // 继承至ObTimerTask,GC的主要执行函数在:ObStoreFile::mark_and_sweep() virtual void runTimerTask() { OB_STORE_FILE.mark_and_sweep(); }};


ObStoreFile::mark_and_sweep 主要逻辑是对所有在用的宏块进行打标,并检查未打标的宏块,将其中 ref_cnt_ 为 0 的进行垃圾回收。


// src/storage/blocksstable/ob_store_file.cpp
void ObStoreFile::mark_and_sweep(){ int ret = OB_SUCCESS; MacroBlockId macro_id; bool is_freed = false;
// 检查GC任务的开关 if (!is_mark_sweep_enabled()) { STORAGE_LOG(INFO, "mark and sweep is disabled, do not mark and sweep this round"); } else { // 标识开始进行gc set_mark_sweep_doing();
// 1. 采用bitmap对所有在使用的宏块进行打标 // a. data块的打标是通过一个宏块迭代器实现的 // b. meta块的打标比较简单,因为可用的meta块是记录在一个数组中的 // 经过打标之后我们就得到所有有效的宏块的bitmap if (OB_FAIL(mark_macro_blocks())) { STORAGE_LOG(WARN, "Fail to mark macro blocks, ", K(ret)); }
// 2. 遍历所有的宏块,进行垃圾清理 begin_time = end_time; if (OB_SUCC(ret)) { for (int64_t i = 0; i < store_file_system_->get_total_macro_block_count() && is_mark_sweep_enabled(); ++i) { if (bitmap_test(i)) { // 有效的宏块,做个二次检查 if (macro_block_info_[i].is_free_) { // BUG, should not happen STORAGE_LOG(ERROR, "the macro block is freed, ", K(i)); } } else { // 无用的宏块,进行GC if (0 == ATOMIC_LOAD(&(macro_block_info_[i].ref_cnt_))) { // ref为0,即可进行清理 if (!macro_block_info_[i].is_free_) { // 释放宏块 // a. 将该宏块信息进行标注:is_free_=true // b. 将该宏块id放入到free_block_array_中,后续进行二次重用 free_block((uint32_t)i, is_freed); // ... } } } } } set_mark_sweep_done(); }}



坏块检查


坏块检查任务也会在 StoreFileGC 线程做,该任务也是一个定时任务,它的定义如下:

class ObFileSystemInspectBadBlockTask : public common::ObTimerTask {public: // 继承至ObTimerTask,主要执行函数在inspect_bad_block virtual void runTimerTask(){ inspect_bad_block(); }private: // 遍历所有的宏块,通过check_macro_block进行宏块检查 // 由于坏块检查比较耗费资源,inspect_bad_block中也做一些流控逻辑 void inspect_bad_block(); // 对某1个宏块进行检查,检查的主要内容有: // a. 宏块id、宏块元数据 // b. 通过check_data_block对宏块数据进行深度检查 int check_macro_block(const ObMacroBlockInfoPair& pair, const storage::ObTenantFileKey& file_key); int check_data_block(const MacroBlockId& macro_id, const blocksstable::ObFullMacroBlockMeta& full_meta, const storage::ObTenantFileKey& file_key);private: // 工具类,宏块检查主要函数,后面会详细分析 ObSSTableMacroBlockChecker macro_checker_;};


坏块检查的代价普遍较高,因为可能需要从磁盘读取所有宏块的内容,并进行 checksum 校验,会占用大量的 CPU、I/O 能力,进而影响业务流量。所以,OceanBase 的坏块检查支持不同级别的检查,DBA 可以根据系统自身的特点,比如磁盘、CPU 的具体配置等,选取合适的级别进行坏块检查,支持级别具体如下。


// src/storage/blocksstable/ob_macro_block_checker.henum ObMacroBlockCheckLevel { CHECK_LEVEL_NOTHING = 0,// 不做坏块检查 CHECK_LEVEL_MACRO, // 检查宏块的checksum,如果宏块的checksum不存在直接返回成功 CHECK_LEVEL_MICRO, // 检查微块的checksum CHECK_LEVEL_ROW, // 检查每个column的checksum,会遍历每一行的每一列 CHECK_LEVEL_AUTO, // 默认的检查级别,如果宏块的checksum存在,就检查宏块,如果宏块的checksum不存在,就检查微块 CHECK_LEVEL_MAX};


接下来我们看下 ObSSTableMacroBlockChecker 的主要内容:


// src/storage/blocksstable/ob_macro_block_checker.h// note: 该class中的函数是线程不安全的class ObSSTableMacroBlockChecker {public: // check是宏块检查主要函数,会根据check_level进行不同级别的检查 // 具体内容的检查见private中的函数 int check(const char* macro_block_buf, const int64_t macro_block_buf_size, const ObFullMacroBlockMeta& meta, ObMacroBlockCheckLevel check_level = CHECK_LEVEL_AUTO);private: // 检查宏块数据buffer的checksum,并和header中checksum中做比较 int check_macro_buf( const ObMacroBlockCommonHeader& common_header, const char* macro_block_buf, const int64_t macro_block_buf_size); // 检查宏块数据的header,具体的检查内容包括: // a. check_sstable_data_header:检查sstable数据的header // b. check_lob_data_header:检查log数据的header // c. check_bloomfilter_data_header:检查header中的bloomfilter int check_data_header(const ObMacroBlockCommonHeader& common_header, const char* macro_block_buf, const int64_t macro_block_buf_size, const ObFullMacroBlockMeta& meta); // 检查普通宏块的具体数据,遍历宏块中每一个微块,微块的检查见下面的check_micro_data int check_data_block(const char* macro_block_buf, const int64_t macro_block_buf_size, const ObFullMacroBlockMeta& meta, const bool need_check_row); // 检查LOB宏块的具体数据,包括其中所有的微块 int check_lob_block( const char* macro_block_buf, const int64_t macro_block_buf_size, const ObFullMacroBlockMeta& meta); // 检查微块的具体数据,包括其中每一行的每一列的checksum int check_micro_data( const char* micro_buf, const int64_t micro_buf_size, const ObFullMacroBlockMeta& meta, int64_t* checksum); // 检查sstable宏块header int check_sstable_data_header( const ObMacroBlockCommonHeader& common_header, const char* macro_block_buf, const ObFullMacroBlockMeta& meta); // 检查LOB宏块header int check_lob_data_header( const ObMacroBlockCommonHeader& common_header, const char* macro_block_buf, const ObFullMacroBlockMeta& meta); // 检查bloomfilter中的header int check_bloomfilter_data_header( const ObMacroBlockCommonHeader& common_header, const char* macro_block_buf, const ObFullMacroBlockMeta& meta);};



相较于宏块、微块的编码来说,宏块的垃圾回收和坏块检查的代码大致逻辑简单易懂,但其中也不乏精巧的细节,比如,垃圾回收中的 bitmap 的使用和坏块检查中的流控,如果你有兴趣的话可以详细阅读上述代码。


我们将在下一篇文章会继续阅读 sstable 的合并、dag 的调度等代码,和你一起交流存储技术,敬请关注!



往期源码解读合集


 为了方便大家了解更多,我们将往期源码解读系列进行了整理汇总,欢迎大家阅读,大家有什么其他想了解的内容,也可以在文章尾部留言,我们在后期努力为大家安排上! 


🔗OceanBase 源码解读(十一):Location Cache 模块浅析


🔗OceanBase 源码解读(十):一号表及其服务寻址


🔗OceanBase 源码解读(九):存储层代码解读之「宏块存储格式」


🔗OceanBase 源码解读(八):事务日志的提交和回放


🔗OceanBase 源码解读(七):一文读懂数据库索引实现原理


🔗OceanBase 源码解读(六):存储引擎详解


🔗OceanBase 源码解读(五):租户的一生


🔗OceanBase 源码解读(四):事务的一生


🔗OceanBase 源码解读(三):分区的一生


🔗OceanBase源码解读(二):SQL的一生


🔗OceanBase源码解读(一):模块结构

戳这里!直达 release note!

您可能也对以下帖子感兴趣

文章有问题?点此查看未经处理的缓存